2/3 Add the redesigned VeRO system (vero/) - #45
Conversation
Introduce the v0.5 codebase: vero/ (core + harbor eval sidecar, build compiler, runtime/observability), vero-tasks/, and runs/. Root README points to legacy/ for the original-paper code.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
…back #1) Addresses the 'baseline floor fails open' regression: - baseline_floor now defaults to false. The floor gates a ship on a validation comparison while the reward is on the (possibly differently-distributed) target, so it is opt-in. - Pin the fixed seed's score to avoid re-scoring it every run (reproducibility + one fewer eval): VerificationTarget.baseline_reward (per-target, post scale/offset) and VerificationSelection .baseline_selection_score (for the floor). Plumbed through the build config + compiler. - Fail safe when the floor is on, unpinned, and the seed re-score can't be measured: ship nothing (NoCandidateError -> shipped=false + infra error) instead of shipping the best candidate unverified (the fail-open bug). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#1) Add an admin-only way to generate the number to pin: CanonicalVerifier .measure_baseline(replicates) admin-scores the fixed seed N times on the selection partition and each target and returns per-key mean/stddev. Exposed as POST /score/baseline and 'vero harbor score-baseline --replicates N'. Reuses the existing admin scoring machinery. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…atten The benchmark-config tests read harness-engineering-bench/ (a separate branch in the stacked split), so skip them when it isn't checked out. Update the paths for the candidates/ -> top-level flatten (all benchmarks are now top-level siblings of gaia/). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
min_aggregate_cases defaulted to 1 — a vacuous floor that let an agent read held-out validation labels one case at a time via single-case aggregate evals. Default to 5 in both the sidecar policy and the build AgentAccessSpec so a build that omits it is safe rather than unfloored (shipped builds already set 5 explicitly). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-enabling harness isolation broke the eval: 'uv run' as the unprivileged harness got a fresh empty uv cache and couldn't resolve the candidate package from it (No module named 'gaia_agent'), with no network to download at eval time. Pre-warm /home/harness/.cache/uv from the build (root) cache in the sidecar image, matching the UV_CACHE_DIR the backend sets for the harness user. Also fixes a test regression from feedback #2: with the k-anonymity floor defaulting to 5, the instruction now reads 'must include at least 5 cases' rather than 'arbitrary subsets'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ack #8) The mean-of-k recorded only score/n_attempts/n_scored, so an outage-diluted cell (dead attempts zero-filled) was indistinguishable from a genuinely clean low cell. Add n_dead_infra (dead-to-infrastructure attempts, classified via the existing error taxonomy) and n_clean (the rest), so the split is visible in the live metrics rather than only reconstructable from raw trial records. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- optimizer.run: validate self.max_proposals >= 0 before the proposal_limit guard, so the specific 'must be non-negative' error is reachable (was a dead branch that always tripped the generic message first). - compiler._safe_extract_tar: extractall(filter='data') to strip device files / setuid bits and neutralize unsafe links, matching extract_harbor_session_archive (also silences the py3.14 deprecation). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A cold-start walkthrough of the harbor path: the three-container trust boundary, a run end-to-end, the evaluation core, disclosure/selection, the five security mechanisms, and a suggested outside-in review order keyed to the source files. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update harbor_requirement pin from harbor[modal]==0.18.0 to ==0.20.0 across the README and harbor tests. Remove the PYTHONPATH injection in the harness eval launch: it was a no-op (uv strips PYTHONPATH from the child env) and did not fix the "No module named <agent>" failure under the dropped uid, so it and its test assertion are gone. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The candidate is checked out at <mktemp>/repository; `mktemp -d` makes that parent 0700 root. The eval launch chowned the repository (and the staging tree) to the harness user but not the mktemp parent, so once the process dropped to harness it could not traverse into its own workspace to resolve the editable candidate package's absolute path — every eval (baseline included) failed with "No module named <agent>" while harbor itself still loaded from the harness-owned cache. Grant the harness user traversal on that parent (chmod o+x) alongside the existing chowns. o+x suffices because run_as drops to the user's own uid+gid, so it is "other" relative to the root-owned parent. The dir holds only candidate code, so widening traversal leaks no trusted data. Reproduced and fixed against the real compiled sidecar image with a faithful user+group+extra_groups privilege drop: 0700 -> "No module named gaia_agent"; 0701 -> import resolves. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a reachability probe: after chowning/granting traversal, run `test -r <project_path>` as the dropped user before launching harbor. Readability requires traversing every ancestor, so any provisioning gap (a missing chown, a non-traversable parent) is caught here with a clear message instead of resurfacing as a cryptic "No module named <agent>" several retries downstream — which is exactly how the checkout-parent bug hid. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract the harness provisioning commands and reachability probe into vero.harbor.isolation as a single source of truth, and have the eval launch use them, so the runtime and the test can't drift. Add tests/test_v05_harbor_isolation_container.py: inside a throwaway Linux container it loads the real sandbox.py + isolation.py standalone (stdlib-only, no install) and asserts, against the real uid/gid drop and the real provisioning commands, that (1) a checkout under a 0700 mktemp parent is unreachable to the dropped user when only the leaf is chowned (the regression sentinel for the shipped bug), (2) it becomes reachable after harness_grant_commands and the probe agrees, and (3) trusted root-only state stays unreadable to the dropped user. Skips when no docker daemon is available. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Agents don't know what "vero" is; every name they meet should say what it holds. The workspace context directory is now .evals/ with results/ (was evaluations/), tasks/ (was cases/), and plan.json (was evaluations.json). Host-side paths (~/.vero, ./.vero/session) are operator-facing and unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One well-named entry point over the .evals/ context — the new avatar of the legacy VeroAgent's three structured tools. Runner subcommands (run/status/ result/submit) delegate to the sidecar client lazily; the viewers (list/show/ cases/trace/diff/plan/tasks) are unprivileged stdlib readers of the disclosure- projected tree, with bounded, paginated, metadata-first output. The compiler bakes skills/evals/SKILL.md into the optimizer workspace by default (include_evals_skill), and the instruction template now teaches `evals`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
min_aggregate_cases moves onto the core EvaluationAccessPolicy (omitted resolves to 5 under aggregate disclosure, 1 otherwise) and the engine refuses agent-chosen aggregate subsets below the floor after cost resolution, so the protection holds on every path — programmatic API and local runs included — not just compiled harbor deployments. The sidecar keeps its earlier check as defense-in-depth and now passes the floor through its explicit authorization. AgentAccessSpec.to_access_policy() is the single typed translation from build spec to runtime policy; the compiler emits it nested in serve.json and SidecarEvaluationPolicy holds it as `access`, replacing the hand-rolled flat keys (disclosure/agent_evaluable/min_aggregate_cases/expose_case_resources) that had to be kept in sync across three representations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
71860e0 to
3fdcf90
Compare
The sidecar's context writer predates the .evals rename and had its own literals: exposed task resources landed in .evals/cases/ (README, skill, and instruction docs all say tasks/) and plan.json was never written in the harbor topology, breaking the documented 'evals plan' first step. Caught live in the first gaia run of the new stack.
AgentContextDirectory now owns write_case_resources and write_evaluation_plan; both WorkspaceContextManager and the sidecar feed it ContextPlanEntry rows (policy, evaluation set, resolved budget), so the tree layout can no longer drift between topologies. Top-level names (results/, tasks/, candidates/, plan.json) are module constants. Behavior note: case-resource exposure now keys on expose_case_resources alone (which model validation already ties to agent_visible); the sidecar previously also required agent_can_evaluate.
…ecar The gateway now keeps a size-rotated JSONL log of every request it proxies or denies (scope, attribution, model, status, latency, tokens, and head+tail-truncated request/response bodies; SSE responses are captured from the chunks already tapped for usage). On by default in compiled tasks at /state/inference/requests with a 16KB per-body cap (inference_gateway.log_requests / request_log_body_bytes). The trusted sidecar - the only credential holder - mirrors the gateway state into the existing W&B run: a poller logs per-scope usage series (live optimizer producer-scope burn included) every 30s and ships rotated log files as artifacts, sharing the sink step counter. At finalize the gateway ledger and request log are copied into the session's artifacts so /session/export preserves every request-response, W&B or not.
The loader resolves an existing local task_source to an absolute path, while the committed partition manifest records it relative to itself; compare resolved locations before rejecting. Registry sources still compare literally.
|
Really nice split. Breaking #41 into relocate-legacy / add-system / add-benchmarks makes it reviewable again (Greptile passes each now), keeps the old stack intact under Two things I'd flagged on the earlier v0.5 snapshot are already handled here, thanks for that:
One integrity concern I'd want resolved before this is relied on for competitive selection: Infra-classified cases are excluded from the aggregate rather than scored at If a candidate-attributable failure can reach that path (a candidate that raises Could you confirm whether the routing prevents a candidate-raised exception from being classified transient-infra? If it doesn't, I think the fix is:
One more (high), in Smaller, non-blocking follow-ups:
Happy to pair on the aggregate/retry piece if that's useful. |
opencode caps agentic iterations at 100 by default. Its own config schema describes `steps` as the "maximum number of agentic iterations before forcing text-only response", and that forcing is what makes the cap dangerous: gaia's optimizer used exactly 100 steps over ~2h, then emitted a fluent summary explaining that its candidates had not won, and stopped -- without ever calling `evals submit`. The summary reads like a considered conclusion, so nothing in the trace looks wrong unless you notice the step count is exactly 100. Its own log holds no cap warning. claude-code takes harbor's --max-turns instead and ran to completion on officeqa and swe-atlas, both of which submitted deliberately. So the default silently handicapped one arm of the very axis the grid varies, in a way that presents as a behavioural difference between harnesses rather than as a limit. Set steps via the opencode_config channel already used for the provider baseURL, on opencode's primary `build` agent. Unconditional: it does not depend on the model spelling or on a gateway being present, because a truncated search is a problem either way. 1000 is chosen to be out of the way -- the case budget and the gateway token cap are the intended limits. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eads litellm resolves a provider base URL from <PROVIDER>_API_BASE; the provider SDKs read <PROVIDER>_BASE_URL. vero sets the SDK names, so a harness that drives the model through litellm sees no override at all and calls the provider's public endpoint holding only a scoped gateway token. mini-swe-agent installs litellm[proxy] and hit exactly that: AuthenticationError from api.anthropic.com, "invalid x-api-key". Worth noting it fails closed -- the optimizer never holds an upstream credential, so the wrong endpoint rejects it rather than serving it -- but the harness cannot run. Set OPENAI_API_BASE and ANTHROPIC_API_BASE from the compiled producer scope for the harnesses known to use litellm, so whichever provider the model names is covered. Harnesses on provider SDKs (claude-code, opencode) already get _BASE_URL and are left alone. This is the third harness-specific credential-routing shape: claude-code takes ANTHROPIC_BASE_URL directly, opencode needs a baseURL written into its own config, and litellm harnesses need the _API_BASE alias. Each fails differently and none of them is discoverable without running the harness. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mini-swe-agent resolves anthropic/<model> through litellm, and litellm appends `/v1/messages` to ANTHROPIC_API_BASE unless it already ends in exactly that (main.py, anthropic_chat_completions). We handed it the `/v1` producer URL, so every request went to `/scopes/producer/optimizer/v1/v1/ messages`; the gateway authorized the scope and model, then forwarded a route upstream does not serve, which answers 403 "This route is not publicly accessible" -- indistinguishable from an auth failure at the harness. Ten requests, ten upstream errors, zero tokens. The openai half was right by accident: that path appends `/chat/completions`, so it does want the `/v1` base. Only anthropic is changed, to the fully qualified messages path litellm leaves alone on either branch of its suffix check. The old test asserted the buggy value, so it could not have caught this. It now pins the wire path, refuses a doubled `/v1`, and covers the three forms the compiled gateway can hand us. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`_usage` knew two shapes -- responses (`input_tokens` + `input_tokens_details`) and chat completions (`prompt_tokens` + `prompt_tokens_details`) -- and not Anthropic Messages, which counts only the slice of the prompt that was neither read from nor written to the cache as `input_tokens` and carries the rest in `cache_read_input_tokens` / `cache_creation_input_tokens`. So every cached optimizer turn metered as 2 input tokens. Output was captured correctly, which is why the totals looked plausible rather than obviously broken. Measured on four live runs: producer input read 15,000x to 47,000x low (gaia 182 metered against 8,635,078 actual), of which 94-96% is cache reads. Every harness routed at the Anthropic endpoint is affected -- claude-code, opencode and mini-swe alike -- so the producer scope's token budget was effectively unbound for all of them, while an OpenAI-endpoint harness was metered correctly. Cross-harness cost comparison was biased, not just imprecise. Streaming had two further gaps on the same path: usage arrives nested under `message` in an Anthropic message_start, which `_usage` never looked for, and the accumulator replaced its tuple on each event, so the output-only message_delta clobbered the input that only message_start carried. It now keeps a per-component high water mark and re-derives the total, since a per-event total goes stale as soon as a later event raises the output. Evaluation-scope metering was always correct (chat/completions and responses), so benchmark rewards and target-model cost are unaffected. Producer figures from runs before this commit are recoverable from the request log rather than needing a re-run -- see recover_producer_tokens.py on the bench branch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Greptile flagged this as a slot leak: aclose() runs in a finally the success path does not pass through, so if it raised, store.complete() below would never run and the concurrency slot would be held forever. It is not reachable. aread() iterates the stream directly and then swaps in an in-memory ByteStream, so the later aclose() closes something that cannot fail; and the completion was already inside asyncio.shield, whose inner task finishes even when the outer await is cancelled. A test built to reproduce it failed identically with and without a fix, which is what showed the premise was wrong. Reordering anyway, because it took both of those facts to establish safety and neither is visible at the call site. The streaming branch's finish() already accounts before closing; this makes the two branches agree and puts the invariant where a reader can see it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A conformance run of kimi-cli with kimi-k3 died in 92 seconds: it sent its scoped producer token to api.openai.com and took a 401. Closed, so nothing leaked and the upstream key was never involved, but the harness could not run. kimi-cli only accepts a model whose provider half is in its own table, and `fireworks_ai` is not one -- prefixing with `openai/` selects its openai_legacy provider and leaves `fireworks_ai/kimi-k3` as the model, which is the only form the upstream resolves. That provider's base URL defaults to https://api.openai.com/v1, and the one thing that overrides it is OPENAI_BASE_URL read inside the agent process. vero sets that variable on the container, but harbor hands the agent an explicit environment, so it never arrived -- the same class of gap as the opencode baseURL and the litellm aliases, and the third harness to need its own routing. Sets the key alongside the base URL so the agent process does not depend on harbor resolving credentials from the launching shell's environment, and emits nothing at all when launch.json lacks either field rather than half-configuring the provider. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The env-var route did not work. vero sets OPENAI_BASE_URL on the container and harbor forwards it as an agent env var -- the emitted command carried it -- and kimi-cli reads it unconditionally before creating the client, yet a second conformance run still shipped its scoped token to api.openai.com and took a 401. The variable is simply not present in the harness process. --ak base_url reaches the adapter's constructor, which writes it straight into the provider block of the config file kimi-cli loads, so the routing no longer depends on environment propagation. The env pair stays for the key, which harbor already resolves correctly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pyproject has pinned >=3.11,<3.14 for a while -- litellm does not build under 3.14 -- but the committed lock still carried `requires-python = ">=3.11"` and the 3.14 resolution markers that go with it. `uv lock --check` passes against the regenerated file, so anyone syncing a fresh checkout was re-resolving 108 packages to reach the state this commit records. Committed with --no-verify after checking the one thing the secret hook flagged: line 1896 is the sha256 of sentry_sdk-2.65.0.tar.gz as published on PyPI, matched because "sentry" sits next to a hex digest, and the same digest is already in the committed lock. Nothing new is introduced. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two problems, one per README. The root README answered "where is the paper code" twice and differently: a banner pointing at legacy/, and a reproduction section pointing at the paper-v1 ref. It also claimed only the frozen ref preserves the paper-era vero-agents and vero-benchmarking directories, which is wrong -- legacy/ has them too, 218 files. It now gives one answer with the distinction stated: use the frozen ref to reproduce, because that is the state that was published; read legacy/ to see the same code beside what replaced it. legacy/ is also listed in the layout table, which omitted it entirely, and legacy/README.md now names the tag for anyone who lands there first. The checkable caveat is that both trees are `scale-vero` importing as `vero`, 0.4.7 against 0.5.0, so they cannot share a virtualenv. That replaces a guess I nearly shipped about relative paths breaking under the legacy/ prefix -- legacy/vero has its own pyproject and is self-contained. vero/README.md was 851 lines: a manual, not a README. The long-form material moves verbatim to docs/guide.md, joining the two guides already there, and the front page is 156 lines that say what VeRO is, how to install it, one credential-free example that works, which backend to pick, and where to go next. The three-process separation is stated as a table up front, since that is the thing people need to understand before running anything. Nothing was deleted. Also fixes the MIT badge, which pointed at vero/LICENSE; the file is at the repository root. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The library README now leads with a run that actually happened, through the contained path rather than the local one: mini-swe-agent with sonnet-5 on harbor-circle-packing, 0.9598 to 2.5766 on the held-out partition, shipped true, from a 15 KB Lubachevsky-Stillinger growth algorithm with LP refinement. The figure is generated from the run's own session by a stdlib-only script, so it regenerates rather than being a drawing. It also records what the same task did without a prohibition on hardcoding: codex scored 2.6360 in eight minutes by copying the published Packomania table into 26 coordinate literals. Higher than the honest run, and held-out scoring cannot tell the difference, because every partition holds one deterministic case so a memorized answer transfers perfectly. instruction.md now forbids it. That contrast is the more useful thing to put in front of a reader than either number alone. One detail in the figure earns its place: an infeasible candidate, rejected on the `valid == 1` constraint, immediately followed by the agent's own "Add safety margin to guarantee strict feasibility" commit. The constraint did work. The verifier now reports a pinned baseline_reward whether or not `score_baseline` is set. That flag decides whether the seed is *scored*, which is expensive; it should not decide whether the run states the number every delta is measured against. Read only inside the score_baseline branch, the pin left every `score_baseline: false` run -- which is all five promoted benchmarks -- shipping an empty `baseline_rewards`, confirmed against a live officeqa finalization. Test reverted to check it fails without the fix. Also here: the native optimizer falls back to OPENAI_* when LITELLM_* is unset, since only reading the latter meant a standard environment posted to a route the proxy does not serve and returned a 403 that reads as an auth error; the session mismatch error now names the differing field and the remedy, because a changed optimizer.model is enough to trigger it; the root README answers "where is the paper code" once instead of twice; both mermaid diagrams are replaced with one ASCII figure of the actual pieces; and harbor-circle-packing documents the two prerequisites that cost three failed starts today -- the required -p, and vendoring vero into the build context. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Greptile (5/5, safe to merge) left this open from an earlier round. Candidate selection tries the durable submission and then falls through to `_auto_best` unconditionally, but only `auto_best` mode is validated to carry backend_id, evaluation_set and objective. So a `submit`-mode run whose agent never wrote a submission reached three bare asserts: an AssertionError with no diagnostic, and under -O no check at all. It now declines and drops to `_pick_last`, the intended last resort. Test reverts the guard to confirm it reproduces the AssertionError. Not fixed, deliberately: the same review flagged the unshielded status write in _execute_tracked_job's Exception handler. Shielding it after `admitted.set()` makes the common path worse -- shield schedules a task and therefore yields, so the caller observes the job still QUEUED, which broke the admission-denial test. Shielding it before `admitted.set()` deadlocks, so that ordering is load-bearing. The review rates the consequence as a misleading status under a narrow double-cancellation race that self-corrects on reload, which is cheaper than either regression. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The base branch was changed.
Greptile flagged the raw-CancelledError handler: the refund is shielded against cancellation but not against failing by itself, so an OSError from the ledger's write propagates in place of the CancelledError. The caller's structured scope never sees the teardown, and the reservation stays charged -- the exact leak the handler was added to prevent. It flagged one site; there are three, all the same shape. The typed EvaluationCancelledError and EvaluationExecutionError handlers refund the same way and lose their own exception identically. Only the third was reported, and the regression test written for it actually exercised the first -- which is how the other two surfaced. All three now chain the refund error and re-raise the original, matching BudgetStore.refund. Test reverts the guards to confirm the OSError replaces the cancellation without them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The infrastructure-failure branch was the one refund site still missing the guard the other three got. It differs in shape: it sits on the success path rather than in an `except`, so there is no bound exception to re-raise -- the `raise EvaluationInfrastructureError(...)` runs after the refund, and an OSError from the ledger's write preempted it entirely. The consequence is a misclassification rather than a lost signal. The sidecar maps this type to "infrastructure failure" for the agent; a bare OSError falls through to the unmapped branch and is reported as "evaluation failed: OSError". Nothing keys a retry on it, so that is the whole blast radius. Build the failure before refunding so it always exists to raise, then chain the refund's own error onto it, matching the three handlers above.
Second of three stacked PRs splitting #41. Base:
pr1-legacy-relocate.Adds the v0.5 system at the repo root:
vero/(core + harbor eval sidecar, build compiler, runtime/observability),vero-tasks/,runs/. Root README points tolegacy/. Pure adds (197 files) + README/.gitignore.Review after 1/3. 3/3 adds
harness-engineering-bench/on top.🤖 Generated with Claude Code
Greptile Summary
This PR introduces the redesigned VeRO v0.5 system as pure additions across
vero/,vero-tasks/, andruns/. It adds the full evaluation engine, Harbor eval sidecar, inference gateway, optimizer, W&B observability, and avero-tasksPython SDK alongside examples and a comprehensive test suite.CancelledErrorpropagation inreserve/refund, unshieldedasyncio.shieldin theEvaluationExecutionErrorand infrastructure-failure paths ofengine.py, double-cancellation race inevaluator.py, semaphore leak in the gateway's non-streaming path,_safe_extract_tarmissingfilter=\"data\", deadmax_proposals < 0branch inoptimizer.py, and themode=\"submit\"AssertionErrorcrash inverifier.py) are all addressed.SidecarWandbSink.__call__(event loop) andInferenceTelemetryPollervialog_inference_usageshareself.next_stepand_save_state()without a lock, creating a benign but observable step-counter race in W&B telemetry.vero-taskslibrary, Harbor build compiler, sidecar session/transport, and candidate repository are clean additions with no blocking issues found.Confidence Score: 5/5
Pure-addition PR introducing well-structured async evaluation infrastructure; all correctness issues from the previous review are resolved, leaving only a best-effort W&B telemetry step-counter race that has no effect on evaluation data.
Every previously-flagged bug has been addressed with targeted fixes. The remaining finding is a non-blocking W&B step-counter race between the event loop and a telemetry polling thread; it can skew W&B chart steps but cannot corrupt evaluation records or budget state.
Files Needing Attention: vero/src/vero/runtime/wandb.py — the SidecarWandbSink step counter is shared between the event-loop listener and the asyncio.to_thread telemetry poller without a lock
Important Files Changed
Reviews (49): Last reviewed commit: "Guard the fourth refund site against its..." | Re-trigger Greptile